Basic FISSA usage

This notebook contains a step-by-step example of how to use the FISSA toolbox. See basic_usage.py (or basic_usage_windows.py for Windows users) for a short example script outside of a notebook interface.


In [1]:
# Import the FISSA toolbox
import fissa

In this notebook, we will be plotting our results using HoloViews, but you can use any plotting library you prefer (matplotlib, bokeh, etc).


In [2]:
# Import our plotting toolbox, and enable options for embedded notebook figures
import holoviews as hv
%load_ext holoviews.ipython
%output widgets='embed'


Defining an experiment

To run a separation step with fissa, you need create a fissa.Experiment object, which will hold your extraction parameters and results.

The inputs to the fissa.Experiment instance are the:

  • experiment images
  • the regions of interest (ROIs) to extract Define your inputs. All that's necessary to define are the image data and ROIs.

Images can be defined as a folder with tiff stacks:

images = 'folder'

Where each tiff stack in the folder is a trial with several frames.

Or the data can also be given as a list of arrays if not stored as tiffs:

images = [array1, array2, array3, ...]

For ROIs either a set of ROIs across all images should be defined, or a set of ROIs per image.

If the ROIs were defined using ImageJ use ImageJ's export function to save them in a zip. Then, indicate the locations as a list:

rois = 'rois.zip'  # for a single set of rois across images
rois = ['rois1.zip', 'rois2.zip', ...]  # for a roiset for each image

Defining a different roiset per image can be useful if you need to adjust for motion drift for example.

Then, we can define out experiment:


In [3]:
# Define image and ROI locations
images_location = 'exampleData/20150529'
rois_location = 'exampleData/20150429.zip'

# Define the folder where FISSA's outputs will be stored, so they can be
# quickly reloaded in the future without having to recompute them.
# Make sure to use a different folder for each experiment.
# This argument is optional; if it is not provided, FISSA will not save its
# results for later use.
output_folder = 'fissa_example'

experiment = fissa.Experiment(images_location, rois_location, output_folder)

Previously analyzed experiments in output_folder will be loaded, if they exist, and the next step could be skipped.

Extracting traces and separating them

Next, we need to extract the traces and separate them:


In [4]:
experiment.separate()


Doing region growing and data extraction....
Doing signal separation....
NMF converged after 1071 iterations.
Finished ROI number 1
NMF converged after 1201 iterations.
NMF converged after 1184 iterations.
Finished ROI number 0
Finished ROI number 3
NMF converged after 1229 iterations.
Finished ROI number 2

If you want to redo preparation and/or separation you can set:

experiment.separate(redo_prep=True, redo_sep=True)

(If you redo prepartion this will also redo the separation, to make sure these always match up).

Accessing results

After running experiment.separate() the results are stored as follows.

ROI outlines

The ROI outlines, as well as the extra neuropil regions, can be found as in experiment.roi_polys as follows. For cell number c and tiff number t, the set of ROIs for that cell and tiff is at

experiment.roi_polys[c][t][0][0]  # basic ROI
experiment.roi_polys[c][t][n][0]  # n = 1, 2, 3, .... the neuropil regions

Sometimes ROIs cannot be expressed as a single polygon (e.g. a ring-ROI), in those cases several polygons are used to describe it as:

experiment.roi_polys[c][t][n][i]  # i iterates over the different polygons

As an example, plotting the first region of interest plus its surrounding neuropil subregions.


In [5]:
# Visualise the ROI polygon for a sample cell
c = 0
t = 0

neuropil1 = hv.Curve(experiment.roi_polys[c][t][1][0])
neuropil2 = hv.Curve(experiment.roi_polys[c][t][2][0])
neuropil3 = hv.Curve(experiment.roi_polys[c][t][3][0])
neuropil4 = hv.Curve(experiment.roi_polys[c][t][4][0])
cell = hv.Curve(experiment.roi_polys[c][t][0][0])

neuropil1 * neuropil2 * neuropil3 * neuropil4 * cell


Out[5]:

In this example, the ROI (grey) is on the boundary of the image, so the surrounding neuropil subregions are arranged to share the available space.

FISSA extracted traces

The final extracted traces can be found in experiment.result as follows. For cell number c and tiff number t, the final extracted trace is given by:

experiment.result[c][t][0, :]

In experiment.result one can find the signals present in the cell ROI, ordered by how strongly they are present (relative to the surrounding regions). experiment.result[c][t][0, :] gives the most strongly present signal, and is seen as the cell's "true" signal. [i, :] for i=1, 2 , 3, ... gives the other signals which are present in the cell ROI.

Before decontamination

The raw extracted signals can be found in experiment.raw in the same way. Now in experiment.raw[c][t][i,:], i indicates the region number, with i=0 being the cell, and i=1, 2, 3, ... indicating the surrounding regions.

As an example, plotting the raw and extracted signals for the second trial for the third cell:


In [6]:
c = 2
t = 1

(hv.Curve(experiment.raw[c][t][0, :], label='Raw')
 * hv.Curve(experiment.result[c, t][0, :], label='Decontaminated')
)


Out[6]:

df/f0

It is often useful to calculate the intensity of a signal relative to the baseline value, df/f0, for the traces. This can be done as follows.

Note that by default, f0 is determined as the minimum across all trials (all tiffs) to ensure that results are directly comparable between trials, but you can normalise each trial individually instead if you prefer by setting across_trials=False.

Since FISSA is very good at removing contamination from the ROI signals, the minimum value on the decontaminated trace will typically be 0.. Consequently, we use the minimum value of the (smoothed) raw signal to provide the f0 from the raw trace for both the raw and decontaminated df/f0.


In [7]:
experiment.calc_deltaf(freq=10, across_trials=True)

In [8]:
c = 2
t = 1

(hv.Curve(experiment.deltaf_raw[c][t], label='Raw', vdims=['df/f0']) *
 hv.Curve(experiment.deltaf_result[c, t][0, :], label='Decontaminated')
)


Out[8]:

Exporting to MATLAB

The results can easily be exported to a MATLAB-compatible matfile as follows.

The output will appear in the output_folder we supplied to experiment when we created it.


In [9]:
experiment.save_to_matlab()

Loading output_folder/matlab.mat in MATLAB will give you three structs, ROIs, raw, and result.

These interface similarly as experiment.ROIs, experiment.raw, and experiment.result described above. However, Matlab counts from 1 (as opposed to Python counting from 0), such that the ROI, raw trace, and decontaminated trace are all found for cell 0 trial 0 as:

ROIs.cell0.trial0{1}  % polygon for the ROI
ROIs.cell0.trial0{2}  % polygon for first neuropil subregion
result.cell0.trial0(1,:)  % final extracted cell signal
result.cell0.trial0(2,:)  % contaminating signal
raw.cell0.trial0(1,:)  % raw measured celll signal
raw.cell0.trial0(2,:)  % raw signal from first neuropil subregion

Addendum

Finding the tiff files

If you find something noteworthy in one of the traces and need to backreference to the corresponding tiff file, you can look up the path to the tiff file with experiment.images.


In [10]:
trial_of_interest = 1

print(experiment.images[trial_of_interest])


exampleData/20150529/AVG_A02.tif

Mean image data

You can get the temporal-mean image for a trial with experiment.means.


In [11]:
hv.Image(experiment.means[trial_of_interest])


Out[11]:

You can also superimpose the cell ROIs on the temporal-mean image as follows.


In [12]:
# Using holoviews

t = trial_of_interest

fig = hv.Raster(experiment.means[t])

for c in range(experiment.nCell):
    roi_poly = experiment.roi_polys[c][t][0][0]
    x = roi_poly[:, 1]
    y = roi_poly[:, 0]
    fig *= hv.Curve(zip(x, y))

fig


Out[12]:

In [13]:
# Using matplotlib

import matplotlib.pyplot as plt

t = trial_of_interest

plt.imshow(experiment.means[t], cmap='gray')
for roi_poly in experiment.roi_polys:
    # Plot border around cells
    # plt.plot(roi_poly[t][0][0][:, 1], roi_poly[t][0][0][:, 0], ':c')
    # Fill cells with partially-transparent shaded area
    plt.fill(roi_poly[t][0][0][:, 1], roi_poly[t][0][0][:, 0], ':r', alpha=0.2)
plt.show()


FISSA customisation settings

FISSA has several user-definable settings, which can be set when defining the experiment object.


In [14]:
# FISSA uses multiprocessing to speed up its processing.
# By default, it will spawn one worker per CPU core on your machine.
# However, if you have a lot of cores and not much memory, you many not
# be able to suport so many workers simultaneously.
# In particular, this can be problematic during the data preparation step
# in which tiffs are loaded into memory.
# The default number of cores for the data preparation and separation steps
# can be changed as follows.
ncores_preparation = 4  # If None, uses all available cores
ncores_separation = None  # if None, uses all available cores

# By default, FISSA uses 4 subregions for the neuropil region.
# If you have very dense data with a lot of different signals per unit area,
# you may wish to increase the number of regions.
nRegions = 8

# By default, each surrounding region has the same area as the central ROI.
# i.e. expansion = 1
# However, you may wish to increase or decrease this value.
expansion = 0.75

# The degree of signal sparsity can be controlled with the alpha parameter.
alpha = 0.1

# Set up a FISSA experiment with these parameters
experiment = fissa.Experiment(
    images_location,
    rois_location,
    output_folder,
    nRegions=nRegions,
    expansion=expansion,alpha=alpha,
    ncores_preparation=ncores_preparation,
    ncores_separation=ncores_separation,
)
# Extract the data with these new parameters.
# Note that we are using the same output folder as before. Since FISSA has cached
# a result alrady to this directory, its default behaviour is to restore the
# previously generated results.
# To make sure FISSA runs a fresh decontamination process with the new parameters,
# we need to make sure to specify to redo the preparation and separation.
# FISSA will then ignore the cached output and overwrite it with new results.
experiment.separate(redo_prep=True)


Reloading previously prepared data...
Reloading previously separated data...
Doing region growing and data extraction....
Doing signal separation....
NMF converged after 1390 iterations.
Finished ROI number 1
NMF converged after 1483 iterations.
Finished ROI number 2
NMF converged after 1509 iterations.
Finished ROI number 0
NMF converged after 1546 iterations.
Finished ROI number 3

We can plot the new results for our example trace from before. Although we doubled the number of neuropil regions around the cell, very little has changed for this example because there were not many sources of contamination.

However, there will be more of a difference if your data has more neuropil sources per unit area within the image.


In [15]:
# Plot the new results
c = 2
t = 1

(hv.Curve(experiment.raw[c][t][0, :], label='Raw')
 * hv.Curve(experiment.result[c, t][0, :], label='Decontaminated')
)


Out[15]:

Alternatively, these settings can be refined after creating the experiment object, as follows.


In [16]:
experiment.ncores_preparation = 8
experiment.alpha = 0.1
experiment.expansion = 0.75

Loading data from large tiff files

By default, FISSA loads entire tiff files into memory at once and then manipulates all ROIs within the tiff. This can sometimes be problematic when working with very large tiff files which can not be loaded into memory all at once. If you have out-of-memory problems, you can activate FISSA's low memory mode, which will cause it to manipulate each tiff file frame-by-frame.


In [17]:
experiment = fissa.Experiment(images_location, rois_location, output_folder, lowmemory_mode=True)


Reloading previously prepared data...
Reloading previously separated data...

Handling custom formats

By default FISSA can use tiff files or numpy arrays as its input image data, and numpy arrays or ImageJ zip files for the ROI definitions. It is also possible to give FISSA a custom script in order to work with other custom and/or proprietary formats that might be used in your lab.

To do so, one must define a python file which implements the same functions as the fissa.datahandler module of FISSA. As an example, see the datahandler_custom.py file in the examples folder.


In [18]:
import datahandler_custom

experiment = fissa.Experiment(
    images_location,
    rois_location,
    output_folder,
    datahandler_custom=datahandler_custom,
)


Reloading previously prepared data...
Reloading previously separated data...